package algocity.modelo.juego;

import algocity.modelo.azar.AzarReal;
import algocity.modelo.catastrofe.Catastrofe;
import algocity.modelo.mapa.Mapa;
import algocity.modelo.mapa.Operador;

import java.io.Serializable;
import java.util.Collection;

public class Jugador
    implements Jugable, Serializable {

    private final Mapa mapa;
    private final String nombre;
    private final MinisterioDeClima ministerioDeClima;
    private final MinisterioDeGanancias ministerioDeGanancias;
    private final MinisterioDePerdidas ministerioDePerdidas;
    private int puntaje;
    private int tiempo;
    private int dinero;
    private Llamador<Object,ObjetoNulo> llamadorAvanceTiempo;

    public Jugador(String nombre, Mapa mapa) throws LongitudNombreJugadorInsuficienteException {
        if ( nombre.length() < 4) {
            throw new LongitudNombreJugadorInsuficienteException("Nombre de jugador '" + nombre + "' inválido");
        }
        this.nombre = nombre;
        this.mapa = mapa;
        this.puntaje = 0;
        this.tiempo = 0;
        this.dinero = Parametros.DINERO_INICIAL;
        llamadorAvanceTiempo = new LlamadorCondicional<Object,ObjetoNulo>();

        // ToDo conviene acá o en Juego ??
        MinisterioDeBienestarSocial ministerioDeBienestarSocial = new MinisterioDeBienestarSocial(mapa);
        registrarAvanceTiempo( new DespertarAlMinisterio(ministerioDeBienestarSocial) );

        ministerioDeGanancias = new MinisterioDeGanancias(ministerioDeBienestarSocial);
        registrarAvanceTiempo( new DespertarAlMinisterio(ministerioDeGanancias));
        ministerioDePerdidas = new MinisterioDePerdidas(mapa);
        registrarAvanceTiempo( new DespertarAlMinisterio(ministerioDePerdidas) );

        registrarAvanceTiempo( new DespertarAlMinisterio( new MinisterioDeHacienda(this) ) );
        registrarAvanceTiempo( new DespertarAlMinisterio( new MinisterioDePuntos(this, ministerioDeBienestarSocial)) );
        ministerioDeClima = new MinisterioDeClima(new AzarReal(), getMapa());
        registrarAvanceTiempo( new DespertarAlMinisterio(ministerioDeClima) );


    }

    public String getNombre() {
        return nombre;
    }

    public int getTiempoActual() {
        return tiempo;
    }

    public void avanzarTiempo() {
        tiempo++;
        llamadorAvanceTiempo.llamar(ObjetoNulo.getInstance());
    }

    /**
     * Registr un observador que será llamado cada vez que se avance el tiempo
     * @param observador Observador a llamar. Debe implementar la interfaz Observador
     */
    public void registrarAvanceTiempo(Operador<Object> observador) {
        llamadorAvanceTiempo.registrar(observador);
    }

    /**
     * Quita del registro los operadores deregistrables de avance de tiempo
     * @return
     */
    public Collection<Operador<Object>> deregistrarAvanceDeTiempo() {
        return llamadorAvanceTiempo.deregistrar();
    }
    public void deregistrarAvanceDeTiempo(Operador<Object> notificador) {
        llamadorAvanceTiempo.deregistrar(notificador);
    };
    /**
     * Registr un observador que será llamado cada vez que se produzca una catástrofe
     * @param observador Observador a llamar. Debe implementar la interfaz Observador
     */
    public void registrarAvisoCatastrofe(Operador<Catastrofe> observador) {
        ministerioDeClima.registrar(observador);
    }

    public Collection<Operador<Catastrofe>> deregistrarAvisoCatastrofe() {
        return ministerioDeClima.deregistrar();
    }

    public void deregistrarAvisoCatastrofe(Operador<Catastrofe> operador) {
        ministerioDeClima.deregistrar(operador);
    }

    @Override
    public void registrarAvisoError(Operador<String> observador) {
        ministerioDeClima.registrarAvisoError(observador);
    }

    public Collection<Operador<String>> deregistrarAvisoError()
    {
        return ministerioDeClima.deregistrarError();
    }

    public void deregistrarAvisoError(Operador<String> observador )
    {
        ministerioDeClima.deregistrarError(observador);
    }

    public void registrarAvisoGanador(Operador<Object> observador) {
        ministerioDeGanancias.registrar(observador);
    }

    public Collection<Operador<Object>> deregistrarAvisoGanador() {
        return ministerioDeGanancias.deregistrar();
    }

    public void deregistrarAvisoGanador(Operador<Object> observer)
    {
        ministerioDeGanancias.deregistrar(observer);
    }

    public void registrarAvisoPerdedor(Operador<Object> observador) {
        ministerioDePerdidas.registrar(observador);
    }

    public Collection<Operador<Object>> deregistrarAvisoPerdedor() {
        return ministerioDePerdidas.deregistrar();
    }

    public void deregistrarAvisoPerdedor(Operador<Object> observer)
    {
        ministerioDePerdidas.deregistrar(observer);
    }

    public int getDinero() {
        return dinero;
    }

    public void agregarDinero(int dinero) {
        this.dinero += dinero;
    }

    public void quitarDinero(int dinero) throws DineroInsuficienteException {

        if ( this.dinero < dinero) {
            throw new DineroInsuficienteException();
        }

        this.dinero -= dinero;
    }

    public int getPuntaje() {
        return puntaje;
    }

    public void agregarPuntaje( int puntos ) {
        puntaje += puntos;
    }

    public Mapa getMapa() {
        return mapa;
    }

    public boolean equals(Object otroObjeto) {
        if ( this == otroObjeto) {
            return true;
        }

        if (!(otroObjeto instanceof  Jugador)) {
            return false;
        }

        Jugador otroJugador = (Jugador) otroObjeto;

        return nombre.equals(otroJugador.nombre) &&
                puntaje == otroJugador.puntaje &&
                tiempo == otroJugador.tiempo &&
                dinero == otroJugador.dinero &&
                mapa.equals( otroJugador.mapa );
    }

}
